package com.leigq.basaltools.util;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.codec.digest.HmacAlgorithms;
import org.apache.commons.codec.digest.HmacUtils;
import org.apache.commons.codec.digest.MessageDigestAlgorithms;
import org.junit.platform.commons.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Base64Utils;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Objects;

/**
 * 基础编码工具类
 * <br/>
 * 包含以下几种加密：
 * <ul>
 *     <li>Base64</li>
 *     <li>URLEncoder、URLDecoder</li>
 *     <li>MD5</li>
 *     <li>MD5加随机盐</li>
 *     <li>SHA</li>
 *     <li>MAC</li>
 * </ul>
 * <p>
 * 注意： <br>
 * Base64加密可逆，一般用来编码信息发送，甚至可以把图片转换成字符串发送到前端显示。注意不要用来发送机密信息！ <br>
 * MD5、SHA、MAC这三种加密算法，是不可逆加密，我们通常只把他们作为加密的基础。单纯的以上三种的加密并不可靠。
 * <p>
 *
 * @author leigq
 */
public abstract class BaseCoderUtils {

    /**
     * The constant log.
     */
    private static final Logger log = LoggerFactory.getLogger(BaseCoderUtils.class);


    /**
     * BASE64加密
     * <br/>
     *
     * @param data 待加密二进制数据
     * @return 加密后字符串 string
     */
    public static String encryptBase64(final byte[] data) {
        return encryptBase64(new String(data));
    }

    /**
     * BASE64加密
     * <br/>
     *
     * @param data 待加密二进制数据
     * @return 加密后字符串 string
     */
    public static String encryptBase64(final String data) {
        return Base64Utils.encodeToString(data.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * BASE64解密
     * <br/>
     *
     * @param base64String 加密后字符串
     * @return 原始二进制数据 byte [ ]
     */
    public static byte[] decryptBase64(final String base64String) {
        return Base64Utils.decodeFromString(base64String);
    }


    /**
     * 将 URL 编码
     *
     * @param data the data
     * @return the string
     */
    public static String encodeUrl(final String data) {
        try {
            return URLEncoder.encode(data, StandardCharsets.UTF_8.name());
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("URL编码异常", e);
        }
    }

    /**
     * 将 URL 解码
     *
     * @param data the data
     * @return the string
     */
    public static String decodeUrl(final String data) {
        try {
            return URLDecoder.decode(data, StandardCharsets.UTF_8.name());
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("URL解码异常", e);
        }
    }


    /**
     * 计算MD5摘要，并以32个字符的十六进制字符串形式返回值
     *
     * @param data the data
     * @return string string
     */
    public static String md5Hex(final byte[] data) {
        return DigestUtils.md5Hex(data);
    }

    /**
     * 计算MD5摘要，并以32个字符的十六进制字符串形式返回值
     *
     * @param data the data
     * @return string string
     */
    public static String md5Hex(final String data) {
        return md5Hex(data.getBytes());
    }

    /**
     * 生成含有随机盐的加密字符串
     *
     * @param data 待加密的字符
     * @return 加密后的字符(含 16 位随机盐) string
     */
    public static String md5Salt(final String data) throws NoSuchAlgorithmException {
        final SecureRandom instanceStrong = SecureRandom.getInstanceStrong();
        StringBuilder sb = new StringBuilder(16);
        sb.append(instanceStrong.nextInt(99999999))
                .append(instanceStrong.nextInt(99999999));
        int len = sb.length();
        if (len < 16) {
            for (int i = 0; i < 16 - len; i++) {
                sb.append("0");
            }
        }
        String salt = sb.toString();
        String md5WithSaltStr = DigestUtils.md5Hex(data + salt);
        char[] cs = new char[48];
        for (int i = 0; i < 48; i += 3) {
            cs[i] = md5WithSaltStr.charAt(i / 3 * 2);
            char c = salt.charAt(i / 3);
            cs[i + 1] = c;
            cs[i + 2] = md5WithSaltStr.charAt(i / 3 * 2 + 1);
        }
        return new String(cs);
    }

    /**
     * 校验密码是否正确
     *
     * @param data       待验证的字符(明文)
     * @param digestData 加密后的字符(含 16 位随机盐)
     * @return 验证结果 boolean
     */
    public static boolean md5SaltVerify(final String data, final String digestData) {
        // 32 位加密字符（不含盐）
        char[] cs1 = new char[32];
        // 16 位的随机盐
        char[] cs2 = new char[16];
        for (int i = 0; i < 48; i += 3) {
            cs1[i / 3 * 2] = digestData.charAt(i);
            cs1[i / 3 * 2 + 1] = digestData.charAt(i + 2);
            cs2[i / 3] = digestData.charAt(i + 1);
        }
        String salt = new String(cs2);
        return Objects.equals(DigestUtils.md5Hex(data + salt), new String(cs1));
    }


    /**
     * sha1 加密，其它 SHA-224、SHA-256、SHA-384、SHA-512、{@link MessageDigestAlgorithms}..., 详见 {@link DigestUtils#} <br>
     *
     * @param data the data
     * @return 加密后16进制字符串 string
     */
    public static String sha1Hex(final byte[] data) {
        return DigestUtils.sha1Hex(data);
    }


    /**
     * sha1 加密，其它 SHA-224、SHA-256、SHA-384、SHA-512、{@link MessageDigestAlgorithms}..., 详见 {@link DigestUtils#} <br>
     *
     * @param data the data
     * @return 加密后16进制字符串 string
     */
    public static String sha1Hex(final String data) {
        return DigestUtils.sha1Hex(data);
    }


    /**
     * sha1 加密，其它 SHA-224、SHA-256、SHA-384、SHA-512、{@link MessageDigestAlgorithms}..., 详见 {@link DigestUtils#} <br>
     *
     * @param data data
     * @return 加密后原始字节数组 byte [ ]
     */
    public static byte[] sha1(final String data) {
        return DigestUtils.sha1(data.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * sha1 加密，其它 SHA-224、SHA-256、SHA-384、SHA-512、{@link MessageDigestAlgorithms}..., 详见 {@link DigestUtils#} <br>
     *
     * @param data data
     * @return 加密后原始字节数组 byte [ ]
     */
    public static byte[] sha1(final byte[] data) {
        return DigestUtils.sha1(data);
    }


    /**
     * 初始化 MAC 密钥
     *
     * @param algorithm 算法，常用算法：HmacSHA1、HmacSHA256、HmacSHA384、HmacSHA512 详见：{@link HmacAlgorithms}
     * @return Base64 编码后的 MAC 密钥
     * @throws NoSuchAlgorithmException the no such algorithm exception
     */
    public static String initMacKey(final String algorithm) throws NoSuchAlgorithmException {
        KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm);
        SecretKey secretKey = keyGenerator.generateKey();
        return encryptBase64(secretKey.getEncoded());
    }


    /**
     * MAC 加密
     *
     * @param algorithm 算法，常用算法：HmacSHA1、HmacSHA256、HmacSHA384、HmacSHA512 详见：{@link HmacAlgorithms}
     * @param data      待加密数据
     * @param key       密钥，可用 {@link BaseCoderUtils#initMacKey(String)} )} 方法生成，也可自定义
     * @return 加密后16进制数据 string
     */
    public static String hmacHex(final String algorithm, final byte[] data, final String key) {
        return Hex.encodeHexString(hmac(algorithm, data, key));
    }


    /**
     * MAC 加密
     *
     * @param algorithm 算法，常用算法：HmacSHA1、HmacSHA256、HmacSHA384、HmacSHA512 详见：{@link HmacAlgorithms}
     * @param data      待加密数据
     * @param key       密钥，可用 {@link BaseCoderUtils#initMacKey(String)} )} 方法生成，也可自定义
     * @return 加密后原始字节数组 byte [ ]
     */
    public static byte[] hmac(final String algorithm, final byte[] data, final String key) {
        HmacUtils hmacMd5 = new HmacUtils(algorithm, key);
        return hmacMd5.hmac(data);
    }


    /**
     * 生成密钥
     *
     * @param seed      种子 偶数
     * @param algorithm the algorithm
     * @param length    the length
     * @return the key
     * @throws DecoderException         the decoder exception
     * @throws NoSuchAlgorithmException the no such algorithm exception
     */
    public static Key initKey(final String seed, final String algorithm, final int length) throws DecoderException, NoSuchAlgorithmException {
        SecureRandom secureRandom;
        if (StringUtils.isNotBlank(seed)) {
            secureRandom = new SecureRandom(Hex.decodeHex(seed));
        } else {
            secureRandom = new SecureRandom();
        }
        // init key生成器
        KeyGenerator kg = KeyGenerator.getInstance(algorithm);
        kg.init(length, secureRandom);
        // 生成一个Key
        return kg.generateKey();
    }

}
